﻿using System;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using UdpAuthorizationService.Core.Logging;

namespace UdpAuthorizationService.Core
{
	public class AuthorizationManager
	{
		private const int Port = 9430;
		private const string IpConfigurationKey = "MulticastIp";
		private readonly IPAddress _multicastGroupAddress;

		private Func<IObservable<byte[]>> _obsFactory;
		private IDisposable _obs;
		private UdpClient _client;


		public AuthorizationManager()
		{
			if (!IPAddress.TryParse(ConfigurationManager.AppSettings[IpConfigurationKey], out _multicastGroupAddress))
				throw new ArgumentException("Configuration key " + IpConfigurationKey + " does not contain a valid multicast IP address");
		}

		public void Start()
		{
			LoggingFacade.Logger.Log(new LogEvent(Severity.Info, "Starting manager"));

			try
			{
				_client = new UdpClient(Port);
				_client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);

				_client.JoinMulticastGroup(_multicastGroupAddress, 50);
				_obsFactory = Observable.FromAsyncPattern(_client.BeginReceive,
											ar =>
											{
												var senderEndPoint = new IPEndPoint(IPAddress.Any, 0);
												return _client.EndReceive(ar, ref senderEndPoint);
											});
				Resume();
			}
			catch (Exception exception)
			{
				LoggingFacade.Logger.Log(new LogEvent(Severity.Error, "Error while starting manager: " + exception.Message));
			}

			LoggingFacade.Logger.Log(new LogEvent(Severity.Info, "Manager started, listening on {0}:{1}", _multicastGroupAddress, Port));
		}

		public void Stop()
		{
			LoggingFacade.Logger.Log(new LogEvent(Severity.Info, "Stopping manager"));
			_client.Close();
			_obs.Dispose();
			_obsFactory = null;
			LoggingFacade.Logger.Log(new LogEvent(Severity.Info, "Manager stopped"));
		}



		public void OnReceive(byte[] data)
		{
			var reference = new byte[] { 0x53, 0x4c, 0, 1, 0 };

			if (data.Take(5).SequenceEqual(reference))
			{
				data[4] = 1;
				var responseClient = new UdpClient();
				responseClient.Send(data, data.Length, new IPEndPoint(_multicastGroupAddress, Port));
				responseClient.Close();

				LoggingFacade.Logger.Log(new LogEvent(Severity.Info, "Response sent to client"));
			}

			Resume();
		}

		private void Resume()
		{
			if (_obs != null)
				_obs.Dispose();

			_obs = _obsFactory.Invoke().Subscribe(OnReceive);
		}

	}
}
